use crate::core::blueprint::Blueprint;
use crate::core::error::blueprint_error::BlueprintError;
use crate::core::error::convert_error::{ConvertError, StrToPathBufSnafu, StrToUriSnafu};
use crate::core::error::ParrotError;
use crate::core::operation::download::Download;
use crate::core::operation::unzip::Unzip;
use crate::core::path_type::TypedPath;
use directories::BaseDirs;
use http::Uri;
use snafu::{ResultExt, Snafu};
use std::path::PathBuf;
use std::str::FromStr;
use uuid::Uuid;
use crate::core::services::job::job::{Job, JobBuilder};

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum Error {
    #[snafu(display("Invalid uri: {source}"))]
    InvalidUri { source: ConvertError },

    #[snafu(display("Invalid dst_dir: {source}"))]
    InvalidDstDir { source: ConvertError },
}

impl From<Error> for BlueprintError {
    fn from(value: Error) -> Self {
        BlueprintError::UpdateDocument { source: value }
    }
}

pub struct UpdateDocument {
    uri: Uri,
    dest_dir: TypedPath,
}

impl Blueprint for UpdateDocument {
    fn build(&self, id: usize) -> Result<Job, BlueprintError> {
        let uuid = Uuid::new_v4().to_string();
        let name = self.uri.path().split("/").last().unwrap_or(&uuid);
        let p = TypedPath::Contextual(name.to_string());
        let job = JobBuilder::new(id)
            .add_step(Download::new(self.uri.clone(), p.clone()))
            .add_step(Unzip::new(p.clone(), self.dest_dir.clone()))
            .build();
        Ok(job)
    }
}

impl UpdateDocument {
    pub fn new(uri: &str, dst_dir: &str) -> Result<Self, Error> {
        let dst_dir_path = if dst_dir.starts_with("~/") || dst_dir.starts_with(r"~\") {
            let base_dirs = BaseDirs::new().expect("Cannot get home directory");
            let home_dir = base_dirs.home_dir();
            home_dir.join(&dst_dir[2..])
        } else {
            PathBuf::from_str(dst_dir)
                .context(StrToPathBufSnafu { path: dst_dir })
                .context(InvalidDstDirSnafu {})?
        };

        Ok(Self {
            uri: Uri::from_str(uri)
                .context(StrToUriSnafu { uri })
                .context(InvalidUriSnafu {})?,
            dest_dir: TypedPath::Normal(dst_dir_path),
        })
    }
}
